package com.tianji.promotion.handler;

import com.tianji.api.client.user.UserClient;
import com.tianji.api.dto.user.UserDTO;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.config.CouponRemindProperties;
import com.tianji.promotion.config.UserRemindDuplicateChecker;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.service.ICouponService;
import com.tianji.promotion.service.ISmsService;
import com.tianji.promotion.service.IUserCouponService;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author Covey
 */
@Component
@Slf4j
@RequiredArgsConstructor
public class CouponExpireReminder {

    private final IUserCouponService userCouponService;

    private final ICouponService couponService;

    private final UserClient userClient;

    private final ISmsService smsService;

    private final CouponRemindProperties remindProperties;

    private final UserRemindDuplicateChecker userRemindDuplicateChecker;


    /**
     * 每小时整点执行，扫描即将过期的优惠券
     */
    @XxlJob("expiringCouponsRemindJob")
    public void scanExpiringCoupons() {
        log.info("开始扫描即将过期的优惠券...");
        // 设置系统管理员认证上下文
        Long adminUserId = 1L;
        try {
            // 设置用户上下文
            // 如果没有用户上下文，会致使userClient调用失败
            UserContext.setUser(adminUserId);
            // 获取需要提醒的时间点：24小时、12小时、1小时前
            for (Integer hours : remindProperties.getRemindHours()) {
                processRemindBatch(hours);
            }
        } finally {
            // 清理用户上下文
            UserContext.removeUser();
        }
        log.info("优惠券过期提醒扫描完成");
    }

    private void processRemindBatch(int hoursBefore) {
        LocalDateTime remindTime = LocalDateTime.now().plusHours(hoursBefore);
        // 查询即将过期的用户券
        List<UserCoupon> expiringCoupons = userCouponService.lambdaQuery()
                .eq(UserCoupon::getStatus, UserCouponStatus.UNUSED)
                .eq(UserCoupon::getExpireRemindSent, false)
                .le(UserCoupon::getTermEndTime, remindTime)
                .gt(UserCoupon::getTermEndTime, LocalDateTime.now())
                // 单次批量处理数量
                .last("LIMIT " + remindProperties.getBatchSize())
                .list();
        // 按用户分组，避免同一用户重复处理
        Map<Long, List<UserCoupon>> userCouponsMap = expiringCoupons.stream()
                .collect(Collectors.groupingBy(UserCoupon::getUserId));
        int successCount = 0;
        int skipCount = 0;
        int errorCount = 0;
        for (Map.Entry<Long, List<UserCoupon>> entry : userCouponsMap.entrySet()) {
            Long userId = entry.getKey();
            List<UserCoupon> userCoupons = entry.getValue();
            try {
                // 只使用用户级别防重复锁
                if (userRemindDuplicateChecker.tryLockUserRemind(userId, hoursBefore)) {
                    // 为同一用户的所有优惠券发送一条合并提醒
                    sendUserRemindMessage(userId, userCoupons, hoursBefore);
                    successCount++;
                    // 直接更新所有用户券的状态，不需要再次加锁
                    for (UserCoupon userCoupon : userCoupons) {
                        updateRemindStatus(userCoupon, hoursBefore);
                    }
                } else {
                    log.debug("用户提醒已发送，跳过处理，userId: {}, hoursBefore: {}", userId, hoursBefore);
                    skipCount += userCoupons.size();
                }
            } catch (Exception e) {
                log.error("处理用户提醒失败，userId: {}", userId, e);
                errorCount += userCoupons.size();
            }
        }
        log.info("批次处理完成，hoursBefore: {}, 成功用户: {}, 跳过用户券: {}, 失败用户券: {}",
                hoursBefore, successCount, skipCount, errorCount);
    }

    private void sendUserRemindMessage(Long userId, List<UserCoupon> userCoupons, int hoursBefore) {
        try {
            // 查询用户信息
            UserDTO userDTO = userClient.queryUserById(userId);
            // 查询所有优惠券信息
            List<Long> couponIds = userCoupons.stream()
                    .map(UserCoupon::getCouponId)
                    .collect(Collectors.toList());
            List<Coupon> coupons = couponService.listByIds(couponIds);
            // 构建合并提醒内容
            String content = buildMergedSmsContent(userDTO, coupons, hoursBefore);
            // 发送短信
            smsService.sendSms(userDTO.getCellPhone(), content, "COUPON_EXPIRE_REMIND");
            log.info("发送合并优惠券过期提醒，用户: {}, 优惠券数量: {}, 剩余时间: {}小时",
                    userDTO.getUsername(), coupons.size(), hoursBefore);
        } catch (Exception e) {
            log.error("发送用户合并提醒失败，userId: {}", userId, e);
            throw e;
        }
    }

    private String buildMergedSmsContent(UserDTO user, List<Coupon> coupons, int hoursBefore) {
        String couponNames = coupons.stream()
                .map(Coupon::getName)
                .collect(Collectors.joining("、"));
        return String.format("【优惠券提醒】尊敬的%s，您的%d张优惠券【%s】将在%d小时后过期，请尽快使用！",
                user.getUsername(), coupons.size(), couponNames, hoursBefore);
    }

    private void updateRemindStatus(UserCoupon userCoupon, int hoursBefore) {
        // 如果是最后一次提醒（1小时前），标记为已发送
        boolean isLastRemind = hoursBefore == 1;
        userCouponService.lambdaUpdate()
                .set(UserCoupon::getLastRemindTime, LocalDateTime.now())
                .set(UserCoupon::getExpireRemindSent, isLastRemind)
                .eq(UserCoupon::getId, userCoupon.getId())
                .update();
        log.debug("更新提醒状态，userCouponId: {}, isLastRemind: {}", userCoupon.getId(), isLastRemind);
    }

    /**
     * 创建模拟用户数据
     */
    private UserDTO createMockUser(Long userId) {
        UserDTO mockUser = new UserDTO();
        mockUser.setId(userId);
        mockUser.setUsername("用户" + (userId % 10000));
        mockUser.setCellPhone("138" + String.format("%08d", userId % 100000000L));
        return mockUser;
    }
}